﻿//***************************************************************
// 类名：资源管理器
// 责任人：钟汶洁
// 修改日期：2015.7
// 功能：下载资源包、管理资源包的依赖关系、读取资源
//***************************************************************

using UnityEngine;
#if UNITY_EDITOR	
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
using Mono.Xml;
using System.Security;

/// <summary>
/// 资源在资源包中的信息
/// </summary>
public class AssetInAssetBundle
{
    /// <summary>
    /// 资源名
    /// </summary>
    public string assetName;
    /// <summary>
    /// 资源所在的资源包名
    /// </summary>
    public string assetBundleName;
}
/// <summary>
/// 资源包信息
/// </summary>
public class AssetBundleInfo
{
    /// <summary>
    /// 资源包名
    /// </summary>
    public string assetBundleName;
    /// <summary>
    /// 资源包路径
    /// </summary>
    public string assetBundlePath;
    /// <summary>
    /// 资源包hash值
    /// </summary>
    public Hash128 assetBundleHash;
}
/// <summary>
/// 已经加载完毕的资源包
/// </summary>
public class LoadedAssetBundle
{
    /// <summary>
    /// 资源包
    /// </summary>
	public AssetBundle m_AssetBundle;
    /// <summary>
    /// 资源包的引用计数，用于自动卸载
    /// </summary>
	public int m_ReferencedCount;

    /// <summary>
    /// 构造
    /// </summary>
    /// <param name="assetBundle">资源包</param>
    public LoadedAssetBundle(AssetBundle assetBundle)
    {
        m_AssetBundle = assetBundle;
        m_ReferencedCount = 1;
    }
}

/// <summary>
/// 资源管理器
/// </summary>
public partial class AssetBundleManager : Kernel<AssetBundleManager>
{
    /// <summary>
    /// 本地资源包根目录
    /// </summary>
	string m_LocalAssetBundleRootPath = "";
    /// <summary>
    /// 远程资源包根目录
    /// </summary>
    string m_RemoteAssetBundleRootPath = "";
    /// <summary>
    /// 资源摘要信息数组
    /// </summary>
	string[] m_Variants = { };
    /// <summary>
    /// 资源包清单信息
    /// </summary>
	AssetBundleManifest m_AssetBundleManifest = null;
#if UNITY_EDITOR	
    /// <summary>
    /// 在编辑器模式下是否模拟资源包加载方式？
    /// </summary>
	static int m_SimulateAssetBundleInEditor = -1;
    const string kSimulateAssetBundles = "SimulateAssetBundles";
#endif

    /// <summary>
    /// 资源信息池（资源名，资源所在的资源包名）
    /// </summary>
    Dictionary<string, string> m_AssetRecords = new Dictionary<string, string>();
    /// <summary>
    /// 资源包信息池（资源包名，资源包信息）
    /// </summary>
    Dictionary<string, AssetBundleInfo> m_AssetBundleInfos = new Dictionary<string, AssetBundleInfo>();
    /// <summary>
    /// 已载入的资源包池（资源包名，资源包）
    /// </summary>
	Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>();
    /// <summary>
    /// 正在载入资源包的WWW对象（资源包名，WWW对象）
    /// </summary>
	Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW>();
    /// <summary>
    /// 正在载入资源包过程中出现的异常（资源包名，异常信息）
    /// </summary>
	Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>();
    /// <summary>
    /// 资源包加载器列表
    /// </summary>
	List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation>();
    /// <summary>
    /// 资源包依赖关系池（资源包名，依赖关系信息）
    /// </summary>
	Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();

    /// <summary>
    /// 资源摘要信息数组
    /// </summary>
    public string[] Variants
    {
        get { return m_Variants; }
        set { m_Variants = value; }
    }

#if UNITY_EDITOR
    /// <summary>
    /// 编辑器模式下是否模拟资源包加载方式？（不需要真正打出资源包，避免每改动一个资源都打一次包的过程）
    /// </summary>
    public static bool SimulateAssetBundleInEditor
    {
        get
        {
            if (m_SimulateAssetBundleInEditor == -1)
            {
                m_SimulateAssetBundleInEditor = EditorPrefs.GetBool(kSimulateAssetBundles, true) ? 1 : 0;
            }

            return m_SimulateAssetBundleInEditor != 0;
        }
        set
        {
            int newValue = value ? 1 : 0;
            if (newValue != m_SimulateAssetBundleInEditor)
            {
                m_SimulateAssetBundleInEditor = newValue;
                EditorPrefs.SetBool(kSimulateAssetBundles, value);
            }
        }
    }

    static int m_UnRecordSimulateInEditor = -1;
    const string UnRecordSimulate = "UnRecodeSimulate";
    /// <summary>
    /// 在编辑器模式下不重新刷 AssetRecords.xml 模拟运行程序
    /// </summary>
    public static bool IsUnRecordSimulate
    {
        get
        {
            if (m_UnRecordSimulateInEditor == -1)
            {
                m_UnRecordSimulateInEditor = EditorPrefs.GetBool(UnRecordSimulate, true) ? 1 : 0;
            }

            return m_UnRecordSimulateInEditor != 0;
        }
        set
        {
            int newValue = value ? 1 : 0;
            if (newValue != m_UnRecordSimulateInEditor)
            {
                m_UnRecordSimulateInEditor = newValue;
                EditorPrefs.SetBool(UnRecordSimulate, value);
            }
        }
    }

#endif

#if UNITY_EDITOR
    /// <summary>
    /// 获取到在不同平台下资源包所在的目录名
    /// </summary>
    /// <param name="target">平台类型</param>
    /// <returns>目录名</returns>
    public static string GetPlatformFolderForAssetBundles(BuildTarget target)
    {
        switch (target)
        {
            case BuildTarget.Android:
                return "Android";
            case BuildTarget.iOS:
                return "iOS";
            case BuildTarget.WebPlayer:
                return "WebPlayer";
            case BuildTarget.StandaloneWindows:
            case BuildTarget.StandaloneWindows64:
                return "Windows";
            case BuildTarget.StandaloneOSXIntel:
            case BuildTarget.StandaloneOSXIntel64:
            case BuildTarget.StandaloneOSXUniversal:
                return "OSX";
            // Add more build targets for your own.
            // If you add more targets, don't forget to add the same platforms to GetPlatformFolderForAssetBundles(RuntimePlatform) function.
            default:
                return null;
        }
    }
#else
    /// <summary>
    /// 获取到在不同平台下资源包所在的目录名
    /// </summary>
    /// <param name="platform">平台类型</param>
    /// <returns>目录名</returns>
    static string GetPlatformFolderForAssetBundles(RuntimePlatform platform)
    {
        switch (platform)
        {
            case RuntimePlatform.Android:
                return "Android";
            case RuntimePlatform.IPhonePlayer:
                return "iOS";
            case RuntimePlatform.WindowsWebPlayer:
            case RuntimePlatform.OSXWebPlayer:
                return "WebPlayer";
            case RuntimePlatform.WindowsPlayer:
                return "Windows";
            case RuntimePlatform.OSXPlayer:
                return "OSX";
            default:
                return null;
        }
    }
#endif

    /// <summary>
    /// 获取本地资源包全路径
    /// </summary>
    /// <param name="platformFolder">资源目录名</param>
    /// <returns>全路径</returns>
    public static string GetLocalAssetBundlesPath(string platformFolder)
    {
        string fullPath = "";
#if UNITY_ANDROID && !UNITY_EDITOR
        fullPath = string.Format("{0}/AssetBundles/{1}/", Application.streamingAssetsPath, platformFolder);
#else
        fullPath = string.Format("file://{0}/AssetBundles/{1}/", Application.streamingAssetsPath, platformFolder);
#endif
        return fullPath;
    }

    /// <summary>
    /// 检测资源是否处于资源记录里面
    /// </summary>
    /// <param name="assetName"></param>
    /// <returns></returns>
    public bool IsInAssetRecords(string assetName)
    {
        return m_AssetRecords.ContainsKey(assetName);
    }

    /// <summary>
    /// 加载出指定资源
    /// </summary>
    /// <param name="assetName">资源名</param>
    /// <param name="type">资源类型</param>
    /// <returns>资源加载器</returns>
    public AssetBundleLoadAssetOperation LoadAssetAsync(string assetName, System.Type type)
    {
        // 在资源记录列表中查找资源在哪个资源包中
        if (!m_AssetRecords.ContainsKey(assetName))
        {
            Debug.LogError(string.Format("There is no asset with name \"{0}\" in asset records dictionary!", assetName));
            return null;
        }
        string assetBundleName = m_AssetRecords[assetName];

        AssetBundleLoadAssetOperation operation = null;
#if UNITY_EDITOR
        // 编辑器中模拟资源包的加载
        if (SimulateAssetBundleInEditor)
        {
            string assetPath = string.Empty;
            if (IsUnRecordSimulate)
            {
                assetPath = m_AssetRecords[assetName];
            }
            else
            {
                string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, assetName);
                if (assetPaths.Length == 0)
                {
                    Debug.LogError("There is no asset with name \"" + assetName + "\" in " + assetBundleName);
                    return null;
                }
                assetPath = assetPaths[0];
            }
            Object target = AssetDatabase.LoadMainAssetAtPath(assetPath);
            operation = new AssetBundleLoadAssetOperationSimulation(target);
        }
        else
#endif
        {
            if (!m_AssetBundleInfos.ContainsKey(assetBundleName))
            {
                Debug.LogError(string.Format("Failed to find the path of asset bundle with name {0}!", assetBundleName));
                return null;
            }
            LoadAssetBundle(assetBundleName);
            operation = new AssetBundleLoadAssetOperationFull(assetBundleName, assetName, type);

            m_InProgressOperations.Add(operation);
        }
        return operation;
    }

    public AssetBundleLoadOperation LoadLevelsAsync(string levelName, bool isAdditive)
    {
        if (!m_AssetRecords.ContainsKey(levelName))
        {
            Debug.LogError(string.Format("There is no asset with name \"{0}\" in asset records dictionary!", levelName));
            return null;
        }
        string assetBundleName = m_AssetRecords[levelName];

        AssetBundleLoadOperation operation = null;

#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string levelPath = string.Empty;
            if (IsUnRecordSimulate)
            {
                levelPath = m_AssetRecords[levelName];
            }
            else
            {
                string[] levelPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, levelName);
                if (levelPaths.Length == 0)
                {
                    Debug.LogError("There is no scene with name \"" + levelName + "\" in " + assetBundleName);
                    return null;
                }
                levelPath = levelPaths[0];
            }

            if (isAdditive)
                EditorApplication.LoadLevelAdditiveInPlayMode(levelPath);
            else
                EditorApplication.LoadLevelInPlayMode(levelPath);

            operation = new AssetBundleLoadLevelSimulationOperation();

        }
        else
#endif
        {
            LoadAssetBundle(assetBundleName);

            operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive);

            m_InProgressOperations.Add(operation);
        }

        return operation;
    }


    /// <summary>
    /// 获取到已加载好的资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
    /// <param name="error">加载资源包过程中出现的异常信息</param>
    /// <returns>已加载好的资源包</returns>
    public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error)
    {
        // 如果有异常，那资源包肯定没加载出来
        if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
        {
            return null;
        }

        // 尝试通过名字从已加载完毕资源包池中去查找资源包
        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle == null)
        {
            return null;
        }

        // 检查一下资源包对应的依赖关系信息，如果有依赖关系，那么返回资源包的前提是它依赖的下层资源包已经加载完毕
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
        {
            return bundle;
        }

        // 看看是不是所有的依赖的资源包也加载了
        for (int i = 0; i < dependencies.Length; ++i)
        {
            if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
            {
                return bundle;
            }

            // 有依赖的资源包未被加载完毕，那么返回空
            LoadedAssetBundle dependentBundle;
            m_LoadedAssetBundles.TryGetValue(dependencies[i], out dependentBundle);
            if (dependentBundle == null)
            {
                return null;
            }
        }

        return bundle;
    }

    /// <summary>
    /// 加载资源包（会顺便加载它依赖的所有资源包）
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
    /// <param name="isLoadDependencies">是否同时加载其依赖的资源包</param>
    public void LoadAssetBundle(string assetBundleName, bool isLoadDependencies = true)
    {
#if UNITY_EDITOR
        // 在编辑器中模拟资源包加载模式时，不需要走真正的加载资源包的流程
        if (SimulateAssetBundleInEditor)
            return;
#endif

        assetBundleName = RemapVariantName(assetBundleName);

        // 加载资源包
        bool isNeedProcessDependencies = LoadAssetBundleInternal(assetBundleName, m_AssetBundleInfos[assetBundleName].assetBundlePath);

        // 加载依赖的资源包
        if (isLoadDependencies && isNeedProcessDependencies)
            LoadDependencies(assetBundleName);
    }

    /// <summary>
    /// 释放资源包及其依赖的资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
    public void UnloadAssetBundle(string assetBundleName)
    {
#if UNITY_EDITOR
        // 在编辑器模拟资源包加载模式时，不存在释放这一说
        if (SimulateAssetBundleInEditor)
        {
            return;
        }
#endif

        // 释放资源包
        UnloadAssetBundleInternal(assetBundleName);
        // 释放其依赖的资源包
        UnloadDependencies(assetBundleName);
    }

    /// <summary>
    /// 加载资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
    /// <param name="assetBundlePath">资源包路径</param>
    /// <returns>需要不需要继续处理资源包的依赖关系？（true：需要，false：不需要）</returns>
    protected bool LoadAssetBundleInternal(string assetBundleName, string assetBundlePath)
    {
        // 如果需要加载得资源包已经被加载过了，那么将其引用计数加1即可
        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.m_ReferencedCount++;
            return false;
        }

        // 已经存在加载该资源包的WWW对象了，那么什么也不需要做，等待其加载完毕就OK
        if (m_DownloadingWWWs.ContainsKey(assetBundleName))
            return false;

        string url = assetBundlePath;
        WWW download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0);
        m_DownloadingWWWs.Add(assetBundleName, download);
        return true;
    }

    /// <summary>
    /// 加载资源包依赖的所有资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
    protected void LoadDependencies(string assetBundleName)
    {
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            return;
        }

        // 获取到资源包的依赖关系信息
        string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
        if (dependencies.Length == 0)
        {
            return;
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            dependencies[i] = RemapVariantName(dependencies[i]);
        }

        // 加载所有依赖的资源包
        m_Dependencies.Add(assetBundleName, dependencies);
        for (int i = 0; i < dependencies.Length; i++)
        {
            LoadAssetBundleInternal(dependencies[i], m_AssetBundleInfos[dependencies[i]].assetBundlePath);
        }
    }

    /// <summary>
    /// 施放资源包所依赖的所有资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
	protected void UnloadDependencies(string assetBundleName)
    {
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
        {
            return;
        }

        // 挨个释放依赖的资源包
        foreach (string dependency in dependencies)
        {
            UnloadAssetBundleInternal(dependency);
        }
        m_Dependencies.Remove(assetBundleName);
    }

    /// <summary>
    /// 释放资源包
    /// </summary>
    /// <param name="assetBundleName">资源包名</param>
	protected void UnloadAssetBundleInternal(string assetBundleName)
    {
        string error;
        LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
        if (bundle == null)
        {
            return;
        }

        if (--bundle.m_ReferencedCount == 0)
        {
            bundle.m_AssetBundle.Unload(false);
            m_LoadedAssetBundles.Remove(assetBundleName);
        }
    }

    // Remaps the asset bundle name to the best fitting asset bundle variant.
    protected string RemapVariantName(string assetBundleName)
    {
        string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();

        // If the asset bundle doesn't have variant, simply return.
        if (System.Array.IndexOf(bundlesWithVariant, assetBundleName) < 0)
            return assetBundleName;

        string[] split = assetBundleName.Split('.');

        int bestFit = int.MaxValue;
        int bestFitIndex = -1;
        // Loop all the assetBundles with variant to find the best fit variant assetBundle.
        for (int i = 0; i < bundlesWithVariant.Length; i++)
        {
            string[] curSplit = bundlesWithVariant[i].Split('.');
            if (curSplit[0] != split[0])
                continue;

            int found = System.Array.IndexOf(m_Variants, curSplit[1]);
            if (found != -1 && found < bestFit)
            {
                bestFit = found;
                bestFitIndex = i;
            }
        }

        if (bestFitIndex != -1)
            return bundlesWithVariant[bestFitIndex];
        else
            return assetBundleName;
    }

    private List<string> m_keysToRemove = new List<string>();

    /// <summary>
    /// 帧更新
    /// </summary>
	void Update()
    {
        // 释放掉所有已下载完毕的WWW对象
        m_keysToRemove.Clear();
        if (m_DownloadingWWWs.Count > 0)
        {
            foreach (KeyValuePair<string, WWW> keyValue in m_DownloadingWWWs)
            {
                WWW download = keyValue.Value;

                // 如果WWW下载过程中出现异常，那么记录下来该WWW对象
                if (download.error != null)
                {
                    m_DownloadingErrors.Add(keyValue.Key, download.error);
                    m_keysToRemove.Add(keyValue.Key);
                    continue;
                }

                // 如果WWW下载完毕，那么将资源包缓存下来，同时记录下该WWW对象
                if (download.isDone)
                {
                    //Debug.Log("Downloading " + keyValue.Key + " is done at frame " + Time.frameCount);
                    m_LoadedAssetBundles.Add(keyValue.Key, new LoadedAssetBundle(download.assetBundle));
                    m_keysToRemove.Add(keyValue.Key);
                }
            }
        }

        // 移除过期的WWW
        for (int i = 0; i < m_keysToRemove.Count; ++i)
        {
            string key = m_keysToRemove[i];
            WWW download = m_DownloadingWWWs[key];
            m_DownloadingWWWs.Remove(key);
            download = null;
        }

        // 更新下载进度信息
        for (int i = 0; i < m_InProgressOperations.Count;)
        {
            if (!m_InProgressOperations[i].Update())
            {
                m_InProgressOperations.RemoveAt(i);
            }
            else
            {
                i++;
            }
        }
    }
}